from lexer import lexer
import sys, inspect, glob, copy

class Interpreter:
    def __init__(self):
        self.instructions = {}
        self.specialInstructions = {}
        
        self.loadSpecialInstructions()
        
    def loadSpecialInstructions(self):
        prefix = "special_"
        for symbol in dir(self):
            if symbol.startswith(prefix) and callable(getattr(self,symbol)):
                self.specialInstructions[symbol[len(prefix):]] = getattr(self,symbol)
                
    def initEnv(self):
        env = {}
        self.loadSystemInstructions(env)
        
        systemFiles = [file[:-3].replace("/",".") for file in glob.glob("system/*.py")]
        self.system_import(systemFiles,env)
        return env
                
    def loadSystemInstructions(self, env):
        prefix = "system_"
        for symbol in dir(self):
            if symbol.startswith(prefix) and callable(getattr(self,symbol)):
                name = symbol[len(prefix):]
                env[name] = getattr(self,symbol)
                
    def runFile(self, filename):
        tree = lexer.lexFile(filename)
        env = self.initEnv()
        return [self.eval(exp,env) for exp in tree]
    
    def runInteractive(self):
        env = self.initEnv()
        self.shutdown = False
        while not self.shutdown:
            print ">",
            for exp in lexer.lexString(raw_input()):
                try:
                    result = self.eval(exp,env)
                    if result != None: print result
                except:
                    print sys.exc_info()[:2]
        
    def eval(self, exp, env):
        data, type = exp
        
        if type == "compound":
            assert len(data), "error: empty compound expression"
                
            op = data[0][0]
            
            if data[0][1] == "simple" and op in self.specialInstructions:
                return self.specialInstructions[op](data[1:],env)
            else:
                return self.apply(self.eval(data[0],env),\
                    [self.eval(arg,env) for arg in data[1:]],env)
            
        elif type == "simple":
            assert data in env, "unbound variable %s" % data
            return env[data]
        
        else:
            return data
        
    def apply(self, proc, args, env):
        assert callable(proc), "%s is not callable" % op
        
        numArgs = len(filter(lambda x: x != "self", inspect.getargspec(proc)[0]))
        if numArgs == 0:
            return proc()
        elif numArgs == 1:
            return proc(args)
        elif numArgs == 2:
            return proc(args,env)
        else:
            raise RuntimeError, "procs can get 0-2 arguments (none, args, or args and env)"
        
    def system_print(self, args):
        print ', '.join(map(str,args))
        
    def system_seq(self, args):
        return args[-1]
    
    def system_exit(self):
        self.shutdown = True
        
    def system_showEnv(self, args, env):
        print env
        
    def system_import(self, args, env):
        for filename in args:
            exec "import %s as module" % filename in locals()
            for name in dir(module):
                if callable(getattr(module,name)):
                    if hasattr(module,'rename') and name in module.rename:
                        env[module.rename[name]] = getattr(module,name)
                    else:
                        env[name] = getattr(module,name)
        
    def special_define(self, args, env):
        assert len(args) == 2, "define takes two args"
        assert args[0][1] == "simple", "define needs a symbol for the first arg"
        env[args[0][0]] = self.eval(args[1],env)
        
    def special_lambda(self, genArgs, genEnv):
        assert len(genArgs) >= 2, "lambda takes two args"
        assert genArgs[0][1] == "compound", "lambda takes an arg list for the first arg"
        for argName, argType in genArgs[0][0]:
            assert argType == "simple", "lambda takes simple arg names in the arg list"
            
        argNames = [argName for argName, argType in genArgs[0][0]]
        procBody = genArgs[1:]
        
        def lambda_impl(callArgs, callEnv):
            assert len(argNames) == len(callArgs),\
                "argument mismatch: wanted %d got %d" % (len(argNames),len(callArgs))
                
            newEnv = copy.copy(callEnv)
            for argName, argVal in zip(argNames,callArgs):
                newEnv[argName] = argVal
                
            [self.eval(exp,newEnv) for exp in procBody[:-1]]
            return self.eval(procBody[-1],newEnv)
            
        return lambda_impl
        
    def special_defun(self, args, env):
        assert len(args) >= 3, "defun takes three args"
        assert args[0][1] == "simple", "defun needs a symbol for the first arg"
        env[args[0][0]] = self.special_lambda(args[1:],env)
        
    def special_if(self, args, env):
        assert len(args) in (2,3), \
            "if takes a condition and a true clause, and an optional false clause"
            
        if self.eval(args[0],env):
            return self.eval(args[1],env)
        elif len(args) == 3:
            return self.eval(args[2],env)
        
    def special_cond(self, args, env):
        assert not len(args)%2, "mismatched cond"
        
        for cond, act in [(args[i],args[i+1]) for i in range(0,len(args),2)]:
            if cond[1] == "simple" and cond[0] == "else":
                return self.eval(act,env)
            elif self.eval(cond,env):
                return self.eval(act,env)
                
interpreter = Interpreter()
